use super::{
    err, err_value, json, ok, throw_err, Base, Client, Collect, ECode, KeyNode, Lock, MapSv, RNull,
    Resp, SvCache, VBase, VMap, Value,
};
use sha2::{Digest, Sha256};

pub struct User;

impl Base for User {}

impl User {
    pub async fn do_cmd(
        &self,
        para: &Value,
        cmd: &str,
        client: &Client,
        cache: SvCache,
        lock: Lock,
    ) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        let collect = Collect::new(client, cache, line);

        match cmd {
            "login" => {
                let sn = data.ck_str("sn")?;
                let passwd = data.ck_str("passwd")?;
                let is_auth = match data.k_bool("auth") {
                    Some(v) => v,
                    None => false,
                };

                let mut user = collect.item_sn("user", sn).await?;
                let mut user = match user.as_object_mut() {
                    Some(v) => v,
                    None => throw_err!(ECode::JsonFormat, "user format error"),
                };

                let old = user.ck_str("passwd")?;

                let ret = self.get_passwd(passwd);

                if old == ret {
                    user.remove("passwd");

                    if is_auth {
                        let _ = self.get_auth(sn, &mut user, &collect).await;
                    }

                    ok!(user)
                } else {
                    err!(ECode::User, "user passwd error")
                }
            }
            "add" => {
                let val = self.chg_passwd(data);
                collect.do_cmd("user", cmd, &val, lock, None).await
            }
            "md" => {
                let val = self.chg_passwd(data);

                collect.do_cmd("user", "mditem", &val, lock, None).await?;

                ok!()
            }
            "list" => {
                let mut user = collect.do_cmd("user", cmd, para, lock, None).await?;
                let user = match user.k_array_mut("data") {
                    Some(v) => v,
                    None => throw_err!(ECode::JsonFormat, "user format error"),
                };

                for d in user.iter_mut() {
                    if let Some(d) = d.as_object_mut() {
                        d.remove("passwd");
                    }
                }

                ok!(user)
            }
            "get" => {
                let mut user = collect.do_cmd("user", cmd, para, lock, None).await?;
                let user = match user.k_obj_mut("data") {
                    Some(v) => v,
                    None => throw_err!(ECode::JsonFormat, "user format error"),
                };

                user.remove("passwd");

                ok!(user)
            }
            _ => collect.do_cmd("user", cmd, para, lock, None).await,
        }
    }

    fn get_passwd(&self, passwd: &str) -> String {
        let mut sec = Sha256::new();
        sec.update(passwd);
        format!("{:X}", sec.finalize())
    }

    fn chg_passwd(&self, data: &MapSv) -> Value {
        let mut val = data.clone();

        if let Some(v) = val.get_mut("v") {
            if let Some(v) = v.as_object_mut() {
                if let Some(passwd) = v.k_str("passwd") {
                    let passwd = self.get_passwd(passwd);
                    v.insert("passwd".to_string(), json!(passwd));
                }
            }
        }

        json!(val)
    }

    async fn get_auth(&self, user_sn: &str, ret: &mut MapSv, collect: &Collect<'_>) -> RNull {
        let dsn = collect.key_all("auth")?;

        let body = collect.list_array(&dsn, "auth", None).await?;
        let body = body.ckarray()?;

        let mut model = Vec::new();
        let mut access = Vec::new();

        for d in body.iter() {
            let sn = d.ck_str("sn")?;

            let user = match collect.item_sub_sn("auth", "user", sn).await {
                Ok(v) => v,
                Err(_) => {
                    continue;
                }
            };

            let user = user.ckarray()?;

            for u in user.iter() {
                let u = u.ckstr()?;
                if u != user_sn {
                    continue;
                }

                if let Some(tmp) = d.k_str("val") {
                    let mut tmp: Vec<&str> = tmp.split(',').map(|x| x.trim()).collect();
                    model.append(&mut tmp);
                }

                if let Some(tmp) = d.k_str("rval") {
                    let mut tmp: Vec<&str> = tmp.split(',').map(|x| x.trim()).collect();
                    access.append(&mut tmp);
                }

                break;
            }
        }

        model.sort_unstable();
        model.dedup();

        access.sort_unstable();
        access.dedup();

        let model = model.join(",");
        let access = access.join(",");

        ret.insert("auth".to_string(), json!(model));
        ret.insert("access".to_string(), json!(access));

        Ok(())
    }
}
